/*
 * Tomer Nuni 300585312
 * Idan Meshita 300043726
 */
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;
/**
 * 
 * 
 * @author root
 *
 */

public class ClipingManger {
	private class PointFloat{
		public float x = 0 ,y = 0;
		
		public PointFloat(Point p){
			x = p.x;
			y = p.y;
		}
		
		public PointFloat(){
			x = 0;
			y = 0;
		}
	}
	
	
	private PointFloat [] normals = new PointFloat[4];
	private PointFloat [] area = new PointFloat[4];
	
	
	public Collection<EdgeByPoints> doCliping(Collection<EdgeByPoints> paintEdges, EdgeByPoints upperBorder, 
								EdgeByPoints lowerBorder, EdgeByPoints rightBorder, EdgeByPoints leftBorder) {
		
		
		
		area[0] = new PointFloat(upperBorder.form);
		area[1] = new PointFloat(upperBorder.to);
		area[2] = new PointFloat(lowerBorder.to);
		area[3] = new PointFloat(lowerBorder.form);
		/*
		normals[0] = new PointFloat(new Point(0, -1));
		normals[1] = new PointFloat(new Point(270, 1));
		normals[2] = new PointFloat(new Point(0, 1));
		normals[3] = new PointFloat(new Point(-60, -1));
		*/
		
		// calculate normal for each border and store them in this.normals
		calcNormals();
		
		
		
		
		// clipped stores the result of the clipping algorithm
		ArrayList<EdgeByPoints> clipped = new ArrayList<EdgeByPoints>();
		
		// for each edge - clip it using the Cyrus-Beck algorithm
		for (EdgeByPoints edge : paintEdges) {
			PointFloat r1 = new PointFloat();
			PointFloat r2 = new PointFloat();
			boolean visible = CBClip(new PointFloat(edge.form), new PointFloat(edge.to), r1, r2);
			if (visible){
				Point p1 = new Point();
				Point p2 = new Point();
				p1.x = (int) r1.x;
				p2.x = (int) r2.x;
				p1.y = (int) r1.y;
				p2.y = (int) r2.y;

				clipped.add(new EdgeByPoints(p1, p2));
			}
		}
		
		
		return clipped;
	}
	
	// calculate dot product
	private float dotProduct(PointFloat v1, PointFloat v2)
	{ 
		return v1.x * v2.x + v1.y * v2.y;
	}

	// calculate normals for each border
	private void calcNormals()
	{ 
		int i,j,k;
		PointFloat v = new PointFloat();
		for (i = 0; i < 4; i++)
		{ 
			j = (i + 1) % 4;
			k = (i + 2) % 4;
			// calculate -1/mI + 1J
			this.normals[i] = new PointFloat();
			// if denominator is zero
			if (this.area[j].x == this.area[i].x){
				this.normals[i].x = -(this.area[j].y - this.area[i].y);
			}
			// else calculate x -1/mI
			else {
				this.normals[i].x = -(this.area[j].y - this.area[i].y)/(this.area[j].x - this.area[i].x);
			}
			this.normals[i].y = 1;
			
			
			// check if inner or outer normal - if inner make it outer
			//v represent the border vector 
			v.x = this.area[k].x - this.area[i].x;
			v.y = this.area[k].y - this.area[i].y;
			// if inner normal - make it outer normal
			if (dotProduct(this.normals[i],v) > 0)
			{
				this.normals[i].x *= -1;
				this.normals[i].y  = -1;
			}
		}
	}

	// clip the line from p1 to p2 according to borders in area[] using the Cyrus-Beck algorithm
	// result in r1, r2
	private boolean CBClip(PointFloat p1, PointFloat p2, PointFloat r1, PointFloat r2)
	{ 
		float t, t1, t2;
		
		PointFloat deltaV = new PointFloat();
		PointFloat v = new PointFloat();
		

		// start largest at smallest legal value and smallest 
		// at largest legal value
		t1 = 0;
		t2 = 1;
		
		// compute the delta vector
		deltaV.x = p2.x - p1.x;
		deltaV.y = p2.y - p1.y;

		boolean visible = true;
		
		int i = 0;
		// clip with each of the 4 borders
		while ((i < 4) && visible)
		{ 
			v.x = p1.x - this.area[i].x;
			v.y = p1.y - this.area[i].y;
			float numerator, denominator;
			numerator = dotProduct(this.normals[i],v);
			denominator = dotProduct(this.normals[i],deltaV);
			t = -(numerator/denominator);
			// parallel
			if (denominator == 0)
			{ 	// if outside
				if (numerator > 0)
				{
					visible = false;
				}
			}
			// 
			else if (denominator < 0.0 || t >= 0.0)
			{ 
				// potentially entering
				if (denominator < 0.0)
				{ 
					if (t <= 1.0){
						// find largest t
						if (t > t1){
							t1 = t;
						}
					}
					else {
						visible = false;
					}
				}
				// potentially exiting
				else{
					if ( t >= 0.0){
						// find the smallest t
						if (t < t2){
							t2 = t;
						}
					}
				}
			}
			else {
				visible = false;
			}
			i++;
		}
		if ( t1 <= t2)
		{ 
			r1.x = p1.x + t1 * deltaV.x;
			r1.y = p1.y + t1 * deltaV.y;
			r2.x = p1.x + t2 * deltaV.x;
			r2.y = p1.y + t2 * deltaV.y;
		}
		else
			visible = false;
		return visible;
	}
	
}
